package com.yzh.springboot_rbac.aspect;

import com.google.common.base.Preconditions;
import com.yzh.springboot_rbac.annotation.IpLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 限流处理器
 */
@Aspect
@Component
public class IpLimterHandler {


    /*手动创建日志器对象：用于输出自定义日志信息
    * 创建步骤：
    * 1.导入slf4j包
    * 2.用日志器工厂类的getLogger方法传入要监控的日志类的.class形式，获得日志器对象
    *
    * 补充一个自定义日志器对象的方法
    * 加入@Slf4j注解，这个注解要引入lombok依赖，这样不用手动创建日志器对象了
    *
    * 最后说一下，这个两种形式都是底层用lomback框架实现的slf4j这个接口，虽然没有引入logback框架依赖，但是
    * springboot的web，starter，redis，内部间接依赖starter，间接就引入了logback这个框架。*/
    private static final Logger LOGGER = LoggerFactory.getLogger(IpLimterHandler.class);

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * DefaultRedisScript：redis核心包里面的默认脚本类，用来读取lua脚本）
     * getRedisScript 读取脚本工具类
     * 这里设置为Long,是因为ipLimiter.lua 脚本返回的是数字类型
     */
    private DefaultRedisScript<Long> getRedisScript;


    /*@PostConstruct：此注解标注的方法会在对象构造方法之后执行，对新构造的方法进行初始化*/
    @PostConstruct
    public void init1() {
    }
    /**
     * 初始化注解
     */
    @PostConstruct
    public void init() {
        /*默认reid脚本类的使用步骤：
        * 1.创建redis脚本类的对象
        * 2.设置获取的脚本返回的类型
        * 3.读取lua脚本
        * 4.使用日志器将自定义日志信息输出到控制台*/
        getRedisScript = new DefaultRedisScript<>();
        getRedisScript.setResultType(Long.class);
        getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("ipLimiter.lua")));
        LOGGER.info("IpLimterHandler[分布式限流处理器]脚本加载完成");
    }

    /**
     * 这个切点可以不要，因为下面的本身就是个注解
     */
//    @Pointcut("@annotation(IpLimiter)")
//    public void rateLimiter() {}

    /**
     * 如果保留上面这个切点，那么这里可以写成
     * @Around("rateLimiter()&&@annotation(ipLimiter)")
     * 限流类型 全局 或者ip
     */
    @Around("@annotation(ipLimiter)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, IpLimiter ipLimiter) throws Throwable {

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("IpLimterHandler[分布式限流处理器]开始执行限流操作");
        }
        Signature signature = proceedingJoinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("the Annotation @IpLimter must used on method!");
        }
        /**
         * 获取注解参数
         */
        // 限流模块IP
        String limitIp = ipLimiter.ipAdress();
        //用于确保 limitIp 不为 null。如果 limitIp 为 null，将抛出 NullPointerException。
        Preconditions.checkNotNull(limitIp);
        // 限流阈值
        long limitTimes = ipLimiter.limit();
        // 限流超时时间
        long expireTime = ipLimiter.time();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("IpLimterHandler[分布式限流处理器]参数值为-limitTimes={},limitTimeout={}", limitTimes, expireTime);
        }
        // 限流提示语
        String message = ipLimiter.message();
        /**
         * 执行Lua脚本
         */
        List<String> ipList = new ArrayList();
        // 设置key值为注解中的值
        ipList.add(limitIp);
        /**
         * 调用脚本并执行
         * excute方法参数：
         * - lua脚本
         * - ip地址集合
         * - 过期时间
         * - 限流次数
         */
        Long result = (Long) redisTemplate.execute(getRedisScript, ipList, expireTime, limitTimes);
        if (result == 0) {
            String msg = "由于超过单位时间=" + expireTime + "-允许的请求次数=" + limitTimes + "[触发限流]";
            LOGGER.debug(msg);
            // 达到限流返回给前端信息
            return message;
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("IpLimterHandler[分布式限流处理器]限流执行结果-result={},请求[正常]响应", result);
        }
        return proceedingJoinPoint.proceed();
    }
}
